﻿namespace Game
{
    public class Board
    {
        private const char k_FirstSymbol = 'A';
        private const int k_MaxDim = 6;
        private const int k_MinDim = 4;        
        private int m_Rows, m_Cols;
        private Card[,] m_CardMatrix;
        private bool[,] m_VisiblityMatrix;
        private bool[,] m_UpturnedMatrix;
        private int m_NumberOfVisibleCards;

        private Board(int i_Rows, int i_Cols)
        {
            m_Rows = i_Rows;
            m_Cols = i_Cols;
            m_CardMatrix = new Card[m_Rows, m_Cols];
            m_VisiblityMatrix = new bool[m_Rows, m_Cols];
            m_UpturnedMatrix = new bool[m_Rows, m_Cols];
            m_NumberOfVisibleCards = 0;

            resetVisbilityMatrices();
            resetCardMatrix();
            scrambleCardMatrix();
        }

        private void resetVisbilityMatrices()
        {
            for (int i = 0; i < m_Rows; ++i)
            {
                for (int j = 0; j < m_Cols; ++j)
                {
                    m_VisiblityMatrix[i, j] = false;
                    m_UpturnedMatrix[i, j] = false;
                }
            }
        }

        private void resetCardMatrix()
        {
            char symbol = k_FirstSymbol;
            int sequence = 2;

            for (int i = 0; i < m_Rows; ++i)
            {
                for (int j = 0; j < m_Cols; ++j)
                {
                    m_CardMatrix[i, j] = new Card(symbol);
                    sequence--;
                    if (sequence == 0)
                    {
                        sequence = 2;
                        symbol++;
                    }
                }
            }
        }

        private void scrambleCardMatrix()
        {
            System.Random rand = new System.Random();
            int randomIndex, currentIndex, maxIndex;
            int randomI, randomJ;
            maxIndex = m_Rows * m_Cols;

            for (int i = 0; i < m_Rows; ++i)
            {
                for (int j = 0; j < m_Cols; ++j)
                {
                    currentIndex = (m_Cols * i) + j;
                    randomIndex = rand.Next(currentIndex, maxIndex);

                    randomI = randomIndex / m_Cols;
                    randomJ = randomIndex % m_Cols;
                    
                    Card temp = m_CardMatrix[i, j];
                    m_CardMatrix[i, j] = m_CardMatrix[randomI, randomJ];
                    m_CardMatrix[randomI, randomJ] = temp;
                }
            }
        }

        public static bool Make(int i_Rows, int i_Cols, out Board o_Board)
        {
            o_Board = new Board(i_Rows, i_Cols);

            return IsGoodDimensions(i_Rows, i_Cols);
        }

        public static bool IsGoodDimensions(int i_Rows, int i_Cols)
        {
            return (i_Cols <= k_MaxDim) &&
                (i_Rows <= k_MaxDim) &&
                (i_Cols >= k_MinDim) &&
                (i_Rows >= k_MinDim) &&
                ((i_Rows % 2 == 0) || (i_Cols % 2 == 0));
        }

        public Card GetCard(Coordinate i_Coord)
        {
            if (!i_Coord.isValid(this))
            {
                throw new System.Exception("Exception: Invalide coordinate at Board.GetCard().");
            }

            return m_CardMatrix[i_Coord.i, i_Coord.j];
        }

        public bool IsLegalMove(Coordinate i_Coordinate)
        {
            return !IsCardVisible(i_Coordinate) && !IsCardUpturned(i_Coordinate);
        }

        public int NumberOfVisibleCards
        {
            get { return m_NumberOfVisibleCards; }
            set { m_NumberOfVisibleCards = value; }
        }

        public int Cols
        {
            get { return m_Cols; }
        }

        public int Rows
        {
            get { return m_Rows; }
        }

        public void FlipCard(Coordinate i_Coord)
        {
            m_VisiblityMatrix[i_Coord.i, i_Coord.j] = !m_VisiblityMatrix[i_Coord.i, i_Coord.j];
            if (m_VisiblityMatrix[i_Coord.i, i_Coord.j] == true)
            {
                m_NumberOfVisibleCards++;
            }
            else 
            {
                m_NumberOfVisibleCards--;
            }
        }

        public void BurnCard(Coordinate i_Coord)
        {
            m_UpturnedMatrix[i_Coord.i, i_Coord.j] = true;
            m_VisiblityMatrix[i_Coord.i, i_Coord.j] = true;
            m_NumberOfVisibleCards++;
        }

        public bool IsCardVisible(Coordinate i_Coord)
        {
            return m_VisiblityMatrix[i_Coord.i, i_Coord.j];
        }

        public bool IsCardUpturned(Coordinate i_Coord)
        {
            return m_UpturnedMatrix[i_Coord.i, i_Coord.j];
        }

        public struct Card
        {
            private char m_Symbol;

            public Card(char i_Symbol)
            {
                m_Symbol = i_Symbol;
            }

            public char Symbol
            {
                get { return m_Symbol; }
                set { m_Symbol = value; }
            }

            public bool Equals(Card i_Other)
            {
                return m_Symbol == i_Other.m_Symbol;
            }
        }

        public Coordinate[] PoolLegalMoves()
        {
            int numberOfLegalCards = (m_Rows * m_Cols) - m_NumberOfVisibleCards;
            Coordinate[] legalCoordinatesPool = new Coordinate[numberOfLegalCards];
            Coordinate CurrentCoordinate;
            int poolIndex = 0;

            for (int i = 0; i < m_Rows; i++)
            {
                for (int j = 0; j < m_Cols; j++)
                {
                    Coordinate.Make(i, j, this, out CurrentCoordinate);
                    if (IsLegalMove(CurrentCoordinate))
                    {
                        legalCoordinatesPool[poolIndex] = CurrentCoordinate;
                        poolIndex++;
                    }
                }
            }

            if (poolIndex != numberOfLegalCards)
            {
                throw new System.Exception("Exception: Pool legal moves failed.");
            }

            return legalCoordinatesPool;
        }

        public struct Coordinate
        {
            private int m_I, m_J;

            private Coordinate(int i_RowIndex, int i_ColIndex)
            {
                m_I = i_RowIndex;
                m_J = i_ColIndex;
            }

            public static bool Make(int i_RowIndex, int i_ColIndex, Board i_Board, out Coordinate o_Coord)
            {
                o_Coord = new Coordinate(i_RowIndex, i_ColIndex);

                return o_Coord.isValid(i_Board);
            }

            public static bool TryParse(string i_InputString, Game.Board i_Board, out Game.Board.Coordinate o_Coordinate)
            {
                bool validOutput = false;
                o_Coordinate = new Coordinate();

                if (i_InputString.Length == 2 && char.IsDigit(i_InputString[0]) && char.IsLetter(i_InputString[1]))
                {
                    int i = (int)(i_InputString[0] - '0') - 1;
                    int j = (int)(char.ToLower(i_InputString[1]) - 'a');

                    validOutput = Coordinate.Make(i, j, i_Board, out o_Coordinate);
                }

                return validOutput;
            }

            public static Coordinate MakeEmpty()
            {
                return new Coordinate(0, 0);
            }
            
            public bool isValid(Board i_Board)
            {
                return (m_I >= 0 && m_I <= i_Board.m_Rows - 1) && (m_J >= 0 && m_J <= i_Board.m_Cols - 1);
            }

            public int i
            {
                get { return m_I; }
                set { m_I = value; }
            }

            public int j
            {
                get { return m_J; }
                set { m_J = value; }
            }
        }

        public bool IsAllBoardUpturned()
        {
            bool returnValue = true;

            foreach (bool currentEntry in m_UpturnedMatrix)
            {
                returnValue = returnValue && currentEntry;
            }

            return returnValue;
        }
    }
}
